from TestExecute import *
from color import *
import nose
from math import *
import media

##############################################################################
# Test functions
##############################################################################


def test_constructor_invalid():
    '''Test that Color constructor fails with invalid RGB value input.'''
    
    nose.tools.assert_raises(ValueError, Color, '', '', '')
    nose.tools.assert_raises(AssertionError, Color, -1, -1, -1)
    nose.tools.assert_raises(AssertionError, Color, 1000, 1000, 1000)
        

def test_constructor():
    '''Test Color constructor. Particularly, that it accepts 
    normalized [0, 255] RGB values, non-normalized values (e.g. 1000), 
    and str values (e.g. "1")'''
    
    # test expectedly normal function calls
    # Color bounds to [0,255] so we must check that the new and old way are
    # the same.
    input_rgb = [(0, 0, 0), (0, 0, 255), (255, 255, 255)]
    
    for idx in range(len(input_rgb)):
        r = input_rgb[idx][0]
        g = input_rgb[idx][1]
        b = input_rgb[idx][2]
        test_color = Color(r, g, b)
        assert test_color.red == r, 'Invalid red component (idx=%s)' % idx
        assert test_color.green == g, 'Invalid green component (idx=%s)' % idx
        assert test_color.blue == b, 'Invalid blue component (idx=%s)' % idx
        
        # test non-int inputs
        test_color = Color(str(r), str(g), str(b))
        assert test_color.red == r, 'Invalid red component (idx=%s)' % idx
        assert test_color.green == g, 'Invalid green component (idx=%s)' % idx
        assert test_color.blue == b, 'Invalid blue component (idx=%s)' % idx
        
        
def test_eq():
    '''Test the __eq__ method of Color'''
    
    colors = [Color(0, 0, 0), Color(255, 255, 255), Color(50, 50, 50)]
    colors_equal = [Color(0, 0, 0), Color(255, 255, 255), Color(50, 50, 50)]
    colors_unequal = [Color(255, 255, 255), Color(0, 0, 0), Color(45, 45, 45)]
    for idx in range(len(colors)):
        assert colors[idx] == colors_equal[idx], \
            "Equal colors not compared properly."
        assert colors[idx] != colors_unequal[idx], \
            "Unequal colors not compared properly."


def test_distance():
    '''Test distance method for finding the euclidean distance between two
    colors.'''
        
    colors1 = [Color(0, 0, 0), Color(255, 255, 255), Color(0, 0, 255),
        Color(255, 255, 255)]
    colors2 = [Color(0, 0, 0), Color(255, 255, 255), Color(255, 0, 0),
        Color(0, 0, 0)]
    
    # Calculate the expected euclidean distance (i.e. sqrt(Rdist ** 2 + Gdist
    # ** 2 + Bdist ** 2))
    expected_distance = [0, 0, sqrt(pow(255, 2) * 2), sqrt(pow(255, 2) * 3)]
    assert len(colors1) == len(colors2) == len(expected_distance), \
        'Test arrays are not mapped 1:1'

    for idx in range(len(colors1)):
        distance1 = colors1[idx].distance(colors2[idx])
        distance2 = colors2[idx].distance(colors1[idx])
        assert distance1 == expected_distance[idx], 'Improper color distance'
        assert distance2 == distance1, \
            'color1.distance(color2) not equal to distance from color2 to color1'
        
        
def test_sub():
    '''Test Color __sub__ method.'''
    
    # test that color subtraction works
    colors1 = [Color(0, 0, 0), Color(255, 255, 255), Color(0, 0, 255), Color(255, 255, 255)]
    colors2 = [Color(0, 0, 0), Color(255, 255, 255), Color(255, 0, 0), Color(0, 0, 0)]
    expected_difference_one_minus_two = [Color(0, 0, 0), Color(0, 0, 0), Color(0, 0, 255), Color(255, 255, 255)]
    expected_difference_two_minus_one = [Color(0, 0, 0), Color(0, 0, 0), Color(255, 0, 0), Color(0, 0, 0)]
    assert len(colors1) == len(colors2) == len(expected_difference_one_minus_two) == len(expected_difference_two_minus_one), \
     'Test arrays are not mapped 1:1'
    for idx in range(len(colors1)):
        # ensure that both difference and subtract work the same
        sub1 = colors1[idx]-colors2[idx]
        sub2 = colors2[idx]-colors1[idx]
        assert sub1 == expected_difference_one_minus_two[idx], 'Unexpected difference returned (one-two)'
        assert sub2 == expected_difference_two_minus_one[idx], 'Unexpected difference returned (two-one)'


def test_add():
    '''Test Color __add__ method.'''
    
    # test that color subtraction works
    colors1 = [Color(1, 1, 1), Color(0, 64, 255), Color(255, 255, 255), Color(34, 78, 12)]
    colors2 = [Color(1, 1, 1), Color(0, 64, 255), Color(255, 255, 255), Color(34, 78, 12)]
    expected_sum = [Color(2, 2, 2), Color(0, 128, 255), Color(255, 255, 255), Color(68, 156, 24)]

    assert len(colors1) == len(colors2) == len(expected_sum), 'Test arrays are not mapped 1:1'
    for idx in range(len(colors1)):
        # ensure that both difference and subtract work the same
        sum = colors1[idx] + colors2[idx]
        assert sum == expected_sum[idx], 'Unexpected sum returned'


def test_set_get_RGB():
    '''Test setting and getting RGB values from Color object.'''
    
    # test the setters/getters of RGB components
    color_arrays = [ [1, 1, 1], [0, 64, 255], [255, 255, 255], [34, 78, 12]]
    test_color = Color(0, 0, 0)
    for color_array in color_arrays:
        # set individually
        test_color.set_red(color_array[0])
        test_color.set_green(color_array[1])
        test_color.set_blue(color_array[2])
        assert test_color.get_rgb() == tuple(color_array)
        assert test_color.red == color_array[0], 'Different red component returned'
        assert test_color.green == color_array[1], 'Different green component returned'
        assert test_color.blue == color_array[2], 'Different blue component returned'
        
def test_eq():
    '''Test __eq__ and __ne__ methods from Color object.'''
    
    # test the setters/getters of RGB components
    colors = [Color(1, 1, 1), Color(0, 64, 255), Color(255, 255, 255), Color(34, 78, 12)]
    colors_eq = [Color(1, 1, 1), Color(0, 64, 255), Color(255, 255, 255), Color(34, 78, 12)]
    colors_not_eq = [Color(0, 0, 0), Color(1, 1, 1), Color(2, 2, 2), Color(3, 78, 12)]
    for idx in range(len(colors)):
        assert colors[idx] == colors_eq[idx]
        assert colors[idx] != colors_not_eq[idx]
        
def test_copy():
    '''Test copy method from Color object.'''
    
    # test the setters/getters of RGB components
    colors = [Color(1, 1, 1), Color(0, 64, 255), Color(255, 255, 255), Color(34, 78, 12)]
    for idx in range(len(colors)):
        color_copy = colors[idx].copy()
        color_copy.set_red(0)
        color_copy.set_green(0)
        color_copy.set_blue(0)
        assert colors[idx] != color_copy


def test_make_lighter_darker():
    '''Test make_lighter and make_darker methods of Color object.'''
    
    # not quite sure how to test this other than comparing the resulting color's values 
    # TODO: revisit this when we allow arbitrary changes in intensity
    color_arrays = [ [1, 1, 1], [0, 64, 255], [255, 255, 255], [34, 78, 12]]
    for color_array in color_arrays:            
        lighter = Color(color_array[0], color_array[1], color_array[2])
        darker = Color(color_array[0], color_array[1], color_array[2])
        lighter.make_lighter()
        darker.make_darker()
        # we expect the lighter color to have values closer to white
        assert lighter.red >= color_array[0], 'Red component is not lighter'
        assert lighter.green >= color_array[1], 'Green component is not lighter'
        assert lighter.blue >= color_array[2], 'Blue component is not lighter'
        # we expect the darker color to have values closer to black            
        assert darker.red <= color_array[0], 'Red component is not darker'
        assert darker.green <= color_array[1], 'Green component is not darker'
        assert darker.blue <= color_array[2], 'Blue component is not darker'
                
                
def test_to_string():
    '''Test __str__ and __repr__ methods of Color object.'''
    
    color_arrays = [ [1, 1, 1], [0, 64, 255], [255, 255, 255], [34, 78, 12]]
    for color_array in color_arrays:
        # compare strings
        expected_repr = 'Color(red=%s, green=%s, blue=%s)' % (color_array[0], color_array[1], color_array[2])
        new_color = Color(color_array[0], color_array[1], color_array[2])
        assert str(new_color) == expected_repr, 'Color strings do not match:\n' + str(new_color) + '\n' + expected_repr
        assert repr(new_color) == expected_repr, 'Color strings do not match:\n' + str(new_color) + '\n' + expected_repr


def test_make_new_color():
    '''Test picture module functions create_color to ensure it creates 
    Color objects identical to ones created manually.'''
    
    color_arrays = [ [1, 1, 1], [0, 64, 255], [255, 255, 255], [34, 78, 12]]
    for color_array in color_arrays:
        # create using all three ways
        color_make = media.create_color(color_array[0], color_array[1], color_array[2])
        color_manual = Color(color_array[0], color_array[1], color_array[2])
        assert color_make == color_manual, 'Colors not the same (' + str(color_manual) + ')'            


def test_non_color_object_call():
    '''Test to ensure that all the picture global convenience functions fail on non-Picture objects.'''
    
    nose.tools.assert_raises(AttributeError, media.distance, DummyClass(), DummyClass())
    nose.tools.assert_raises(AttributeError, media.distance, Color(0,0,0), DummyClass())
    nose.tools.assert_raises(AttributeError, media.distance, DummyClass(), Color(0,0,0))
    nose.tools.assert_raises(AttributeError, media.darken, DummyClass())
    nose.tools.assert_raises(AttributeError, media.lighten, DummyClass())

    
if __name__ == '__main__':
    nose.runmodule()